package org.cancer.hadesdata.syno;

import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import org.cancer.hadesdata.exception.SynoException;
import org.cancer.hadesdata.model.FileType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;

@Service
public class SynoManager {
	public enum FileSort {
		DIR, FILE, ALL
	}

	public enum ImageSize {
		SMALL("small"), MEDIUM("medium"), LARGE("large"), ORIGINAL("original");
		private String size;

		private ImageSize(String size) {
			this.size = size;
		}

		public String getSize() {
			return size;
		}

		public void setSize(String size) {
			this.size = size;
		}
	}

	public enum DownloadMode {
		OPEN("open"), DOWNLOAD("download");

		private String mode;

		private DownloadMode(String mode) {
			this.mode = mode;
		}

		public String getMode() {
			return mode;
		}

		public void setMode(String mode) {
			this.mode = mode;
		}
	}

	@Autowired
	private RestTemplate restTemplate;

	@Autowired
	private SynoConfig synoConfig;

	private static Map<String, SynoApiInfo> apiInfomation = null;
	private static String sid = null;

	public <T> T synoPost(String cgiPath, String apiName, int version, String method, Map<String, String> params,
			Class<T> tClass) {
		T postValue = null;
		StringBuffer urlBuffer = new StringBuffer(
				synoConfig.getHostapi() + cgiPath + "?api=" + apiName + "&version=" + version + "&method=" + method);
		if (params != null) {
			Iterator<String> it = params.keySet().iterator();
			while (it.hasNext()) {
				String key = it.next();
				String value = params.get(key);
				urlBuffer.append("&" + key + "=" + value);
			}
		}
		postValue = this.restTemplate.getForObject(urlBuffer.toString(), tClass);
		return postValue;
	}

	public String synoUrl(String apiName, String method, Map<String, String> params, String sid) {
		SynoApiInfo api = apiInfomation.get(apiName);
		StringBuffer urlBuffer = new StringBuffer(
				synoConfig.getHostapi() + api.getPath() + "?api=" + apiName + "&version=" + api.getMaxVersion() + "&method=" + method);
		if (params != null) {
			Iterator<String> it = params.keySet().iterator();
			while (it.hasNext()) {
				String key = it.next();
				String value = params.get(key);
				urlBuffer.append("&" + key + "=" + value);
			}
		}
		if (sid != null)
			urlBuffer.append("&_sid=" + sid);
		return urlBuffer.toString();
	}

	public <T> T synoGet(String apiName, String method, Map<String, String> params, String sid, Class<T> tClass) {
		T postValue = null;
		SynoApiInfo api = apiInfomation.get(apiName);
		StringBuffer urlBuffer = new StringBuffer(
				synoConfig.getHostapi() + api.getPath() + "?api=" + apiName + "&version=" + api.getMaxVersion() + "&method=" + method);
		if (params != null) {
			Iterator<String> it = params.keySet().iterator();
			while (it.hasNext()) {
				String key = it.next();
				String value = params.get(key);
				urlBuffer.append("&" + key + "=" + value);
			}
		}
		if (sid != null)
			urlBuffer.append("&_sid=" + sid);
		postValue = this.restTemplate.getForObject(urlBuffer.toString(), tClass);
		return postValue;
	}

	private String getSid(String account, String password) throws SynoException {
		Map<String, String> params = Maps.newHashMap();
		params.put("account", account);
		params.put("passwd", password);
		params.put("session", "FileStation");
		params.put("format", "sid");
		String strJson = this.synoGet("SYNO.API.Auth", "login", params, null, String.class);
		return this.getJson(strJson).getAsJsonObject().get("sid").getAsString();
	}

	private JsonObject getJson(String strJson) throws SynoException {
		JsonParser parser = new JsonParser();
		JsonObject json = (JsonObject) parser.parse(strJson);
		if (json.get("success").getAsString().equals("true"))
			return json.get("data").getAsJsonObject();
		else
			throw new SynoException(json.get("error").getAsJsonObject().get("code").getAsString());
	}

	public void synoGetApiInfo(String[] infoName) {
		String names = "";
		for (int i = 0; i < infoName.length; i++)
			names += "," + infoName[i];
		names = names.substring(1) + ".";
		Map<String, String> map = Maps.newHashMap();
		map.put("query", names);
		String strJson = this.synoPost("query.cgi", "SYNO.API.Info", 1, "query", map, String.class);
		try {
			JsonObject json = this.getJson(strJson);
			Set<Entry<String, JsonElement>> sets = json.entrySet();
			Iterator<Entry<String, JsonElement>> it = sets.iterator();
			while (it.hasNext()) {
				Entry<String, JsonElement> obj = it.next();
				JsonObject jsonInfo = (JsonObject) obj.getValue();
				SynoApiInfo info = new SynoApiInfo();
				info.setMaxVersion(jsonInfo.get("maxVersion").getAsInt());
				info.setMinVersion(jsonInfo.get("minVersion").getAsInt());
				info.setPath(jsonInfo.get("path").getAsString());
				apiInfomation.put(obj.getKey(), info);
			}
		} catch (SynoException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	@SuppressWarnings("unchecked")
	public <T> T synoDataExecute(String apiName, String method, Map<String, String> params, Class<T> tClass) {
		if (apiInfomation == null) {
			apiInfomation = Maps.newHashMap();
			this.synoGetApiInfo(new String[] { "SYNO.API.Auth", "SYNO.FileStation" });
		}
		if (sid == null)
			try {
				sid = this.getSid(synoConfig.getUserName(), synoConfig.getPassword());
			} catch (SynoException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		if (tClass == null)
			return (T) this.synoUrl(apiName, method, params, sid);
		else
			return this.synoGet(apiName, method, params, sid, tClass);
	}

	public List<FileType> list(String folderPath) throws SynoException {
		List<FileType> list = Lists.newArrayList();
		Map<String, String> maps = null;
		String method = "list_share";
		String keys = "shares";
		if (folderPath != null) {
			maps = Maps.newHashMap();
			maps.put("folder_path", "\"" + folderPath + "\"");
			method = "list";
			keys = "files";
		}
		JsonObject json = this.getJson(this.synoDataExecute("SYNO.FileStation.List", method, maps, String.class));
		JsonArray array = json.get(keys).getAsJsonArray();
		Iterator<JsonElement> it = array.iterator();
		while (it.hasNext()) {
			JsonObject obj = (JsonObject) it.next();
			FileType file = null;
			file = new FileType();
			//file.setId(StringEscape.escapeHtml(obj.get("path").getAsString()));
			file.setName(obj.get("name").getAsString());
			file.setUrl(obj.get("path").getAsString());
			if (obj.get("isdir").getAsBoolean()) {
				file.setDir(true);
			} else {
				file.setDir(false);
			}
			list.add(file);
		}
		return list;
	}

	public String image(String filePath, ImageSize size) {
		String method = "get";
		Map<String, String> maps = null;
		if (filePath != null) {
			maps = Maps.newHashMap();
			maps.put("path", "\"" + filePath + "\"");
			maps.put("size", "\"" + size.getSize() + "\"");
		}
		String imageUrl = this.synoDataExecute("SYNO.FileStation.Thumb", method, maps, null);
		return imageUrl;
	}

	public String download(String filePath, DownloadMode mode) {
		String method = "download";
		Map<String, String> maps = null;
		if (filePath != null) {
			maps = Maps.newHashMap();
			maps.put("path", "\"" + filePath + "\"");
			maps.put("mode", "\"" + mode.getMode() + "\"");
		}
		return this.synoDataExecute("SYNO.FileStation.Download", method, maps, null);
	}
}

class SynoApiInfo {
	private int maxVersion = 1;
	private int minVersion = 1;
	private String path = null;

	public int getMaxVersion() {
		return maxVersion;
	}

	public void setMaxVersion(int maxVersion) {
		this.maxVersion = maxVersion;
	}

	public int getMinVersion() {
		return minVersion;
	}

	public void setMinVersion(int minVersion) {
		this.minVersion = minVersion;
	}

	public String getPath() {
		return path;
	}

	public void setPath(String path) {
		this.path = path;
	}
}